using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SQLite;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;

namespace Rapier.Kodachi.Shared
{
    public class DataManager
    {
        #region Data Members
        private const string dbFilename = "Kodachi.kdb";
        private string connectionString; 
        #endregion

        #region Constructor
        public DataManager()
        {
            SQLiteConnectionStringBuilder csb = new SQLiteConnectionStringBuilder();
            csb.DataSource = Path.Combine(Application.StartupPath, dbFilename);
            connectionString = csb.ConnectionString;
        }
        #endregion

        #region Public Methods
        public void SaveSettings(Plugin plugin, object settingsObject)
        {
            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                connection.Open();
                SQLiteTransaction transaction = connection.BeginTransaction();
                string delete = String.Format("DELETE FROM Settings WHERE pluginGuid = '{0}'", plugin.Guid);
                SQLiteCommand command = new SQLiteCommand(delete, connection, transaction);
                try
                {
                    command.ExecuteNonQuery();
                    string objectXml = ConvertObjectToXml(settingsObject);
                    string insert = String.Format("INSERT INTO Settings VALUES('{0}', '{1}')", plugin.Guid, objectXml);
                    command.CommandText = insert;
                    command.ExecuteNonQuery();
                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    Debug.Fail("Failed saving settings.", ex.Message);
                    transaction.Rollback();
                }
                finally
                {
                    if (command != null)
                        command.Dispose();
                }
            }
        }

        public T LoadSettings<T>(Plugin plugin)
        {
            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                connection.Open();
                string delete = String.Format("SELECT objectXml FROM Settings WHERE pluginGuid = '{0}'", plugin.Guid);
                SQLiteCommand command = new SQLiteCommand(delete, connection);
                try
                {
                    object xml = command.ExecuteScalar();
                    if (xml == null)
                        return default(T);

                    string xmlString = xml.ToString();
                    return ConvertObjectFromXml<T>(xmlString);
                }
                catch (Exception ex)
                {
                    Debug.Fail("Failed loading settings.", ex.Message);
                }
                finally
                {
                    if (command != null)
                        command.Dispose();
                }

                return default(T);
            }
        }

        public void SaveBinaryData(Plugin plugin, string key, byte[] bytes)
        {
            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                connection.Open();
                SQLiteTransaction transaction = connection.BeginTransaction();
                string delete = String.Format("DELETE FROM BinaryData WHERE pluginGuid = '{0}' AND userKey='{1}'", plugin.Guid.ToString(), key);
                SQLiteCommand command = new SQLiteCommand(delete, connection, transaction);
                try
                {
                    command.ExecuteNonQuery();
                    string insert = "INSERT INTO BinaryData VALUES(@guid, @key, @data)"; 
                    command.CommandText = insert;
                    command.Parameters.Add(new SQLiteParameter("guid", plugin.Guid.ToString()));
                    command.Parameters.Add(new SQLiteParameter("key", key));
                    command.Parameters.Add(new SQLiteParameter("data", bytes));
                    command.ExecuteNonQuery();
                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    Debug.Fail("Failed saving data.", ex.Message);
                    transaction.Rollback();
                }
                finally
                {
                    if (command != null)
                        command.Dispose();
                }
            }
        }

        public byte[] LoadBinaryData(Plugin plugin, string key)
        {
            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                connection.Open();
                string select = String.Format("SELECT bytes FROM BinaryData WHERE pluginGuid = '{0}' AND userKey='{1}'", plugin.Guid, key);
                SQLiteCommand command = new SQLiteCommand(select, connection);
                try
                {
                    byte[] data = (byte[])command.ExecuteScalar();
                    return data;
                }
                catch (Exception ex)
                {
                    Debug.Fail("Failed loading data.", ex.Message);
                    return null;
                }
                finally
                {
                    if (command != null)
                        command.Dispose();
                }
            }
        }

        public void DeleteBinaryData(Plugin plugin, string key)
        {
            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                connection.Open();
                string delete = String.Format("DELETE FROM BinaryData WHERE pluginGuid = '{0}' AND userKey='{1}'", plugin.Guid.ToString(), key);
                SQLiteCommand command = new SQLiteCommand(delete, connection);
                try
                {
                    command.ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    Debug.Fail("Failed deleting data.", ex.Message);
                }
                finally
                {
                    if (command != null)
                        command.Dispose();
                }
            }
        }

        public byte[] ConvertObjectToByteArray(object obj)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(ms, obj);
                return ms.ToArray();
            }
        }

        public T ConvertObjectFromByteArray<T>(byte[] data)
        {
            using (MemoryStream ms = new MemoryStream(data))
            {
                BinaryFormatter bf = new BinaryFormatter();
                return (T)bf.Deserialize(ms);
            }
        }
        #endregion

        #region Private Methods
        private string ConvertObjectToXml(object obj)
        {
            string xml = String.Empty;
            XmlSerializer xs = new XmlSerializer(obj.GetType());

            using (MemoryStream ms = new MemoryStream())
            {
                xs.Serialize(ms, obj);
                byte[] buffer = ms.GetBuffer();
                ms.Write(buffer, 0, buffer.Length);
                ASCIIEncoding aEncoding = new ASCIIEncoding();
                xml = aEncoding.GetString(buffer).Replace("\0", String.Empty);
            }

            return xml;
        }

        private T ConvertObjectFromXml<T>(string xml)
        {
            object obj = null;

            XmlSerializer xs = new XmlSerializer(typeof(T));
            ASCIIEncoding aEncoding = new ASCIIEncoding();

            using (MemoryStream ms = new MemoryStream(aEncoding.GetBytes(xml)))
            {
                try
                {
                    obj = xs.Deserialize(ms);
                    if (obj == null)
                        return default(T);
                }
                catch (Exception ex)
                {
                    Debug.Fail("Failed converting XML to object", ex.Message);
                }
                finally
                {
                    ms.Close();
                }
            }

            return (T)obj;
        }
        #endregion
    }
}
